"""
03_relationships.py
===================
Relationships in Neo4j

This module covers:
1. Creating relationships
2. Relationship types and properties
3. Bidirectional vs directional relationships
4. Relationship patterns
5. Querying relationships

Author: Neo4j Tutorial
Date: January 2025
"""

from typing import Dict, List, Any, Optional
from dataclasses import dataclass, field
from datetime import datetime, date
from enum import Enum
import sys
import os

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from src.utils.connection import get_driver


class RelationshipType(Enum):
    """Standard relationship types for healthcare domain."""
    # Patient relationships
    DIAGNOSED_WITH = "DIAGNOSED_WITH"
    TAKES = "TAKES"
    ALLERGIC_TO = "ALLERGIC_TO"
    TREATED_BY = "TREATED_BY"
    
    # Medication relationships
    TREATS = "TREATS"
    INTERACTS_WITH = "INTERACTS_WITH"
    CONTRAINDICATED_FOR = "CONTRAINDICATED_FOR"
    
    # Disease relationships
    RELATED_TO = "RELATED_TO"
    COMPLICATION_OF = "COMPLICATION_OF"
    
    # Document relationships
    MENTIONS = "MENTIONS"
    HAS_CHUNK = "HAS_CHUNK"
    NEXT = "NEXT"


@dataclass
class Diagnosis:
    """Diagnosis relationship properties."""
    diagnosed_date: date
    diagnosed_by: str
    status: str = "active"  # active, resolved, chronic
    severity: str = "moderate"
    notes: Optional[str] = None


@dataclass
class Prescription:
    """Prescription relationship properties."""
    prescribed_date: date
    prescribed_by: str
    dosage: str
    frequency: str
    duration_days: int
    refills: int = 0
    status: str = "active"


class RelationshipManager:
    """
    Manages relationships in Neo4j.
    
    Relationship Structure:
    
    ┌──────────┐                              ┌──────────┐
    │ Patient  │──[DIAGNOSED_WITH {           │ Disease  │
    │          │    diagnosedDate: date(),    │          │
    │  P001    │    diagnosedBy: "Dr. Smith", │  E11     │
    │          │    status: "active",         │          │
    │          │    severity: "moderate"      │          │
    └──────────┘  }]─────────────────────────>└──────────┘
    
    Key Concepts:
    - Relationships are DIRECTED (have a start and end node)
    - Relationships can have properties like nodes
    - Relationship types are UPPERCASE by convention
    - A node can have multiple relationships of same type
    """
    
    def __init__(self, driver=None):
        self.driver = driver or get_driver()
    
    # =========================================================
    # CREATE RELATIONSHIPS
    # =========================================================
    
    def create_diagnosis(
        self,
        patient_id: str,
        disease_id: str,
        diagnosis: Diagnosis
    ) -> Dict[str, Any]:
        """
        Create DIAGNOSED_WITH relationship.
        
        Args:
            patient_id: Patient node ID
            disease_id: Disease node ID (ICD-11 code)
            diagnosis: Diagnosis properties
            
        Returns:
            Relationship data
        """
        query = """
        MATCH (p:Patient {id: $patient_id})
        MATCH (d:Disease {id: $disease_id})
        CREATE (p)-[r:DIAGNOSED_WITH {
            diagnosedDate: date($diagnosed_date),
            diagnosedBy: $diagnosed_by,
            status: $status,
            severity: $severity,
            notes: $notes,
            createdAt: datetime()
        }]->(d)
        RETURN p.name as patient, d.name as disease, properties(r) as relationship
        """
        
        with self.driver.session() as session:
            result = session.run(
                query,
                patient_id=patient_id,
                disease_id=disease_id,
                diagnosed_date=diagnosis.diagnosed_date.isoformat(),
                diagnosed_by=diagnosis.diagnosed_by,
                status=diagnosis.status,
                severity=diagnosis.severity,
                notes=diagnosis.notes
            )
            record = result.single()
            return record.data() if record else None
    
    def create_prescription(
        self,
        patient_id: str,
        medication_id: str,
        prescription: Prescription
    ) -> Dict[str, Any]:
        """
        Create TAKES relationship (prescription).
        
        Args:
            patient_id: Patient node ID
            medication_id: Medication node ID (RxNorm code)
            prescription: Prescription properties
            
        Returns:
            Relationship data
        """
        query = """
        MATCH (p:Patient {id: $patient_id})
        MATCH (m:Medication {id: $medication_id})
        CREATE (p)-[r:TAKES {
            prescribedDate: date($prescribed_date),
            prescribedBy: $prescribed_by,
            dosage: $dosage,
            frequency: $frequency,
            durationDays: $duration_days,
            refills: $refills,
            status: $status,
            createdAt: datetime()
        }]->(m)
        RETURN p.name as patient, m.name as medication, properties(r) as relationship
        """
        
        with self.driver.session() as session:
            result = session.run(
                query,
                patient_id=patient_id,
                medication_id=medication_id,
                prescribed_date=prescription.prescribed_date.isoformat(),
                prescribed_by=prescription.prescribed_by,
                dosage=prescription.dosage,
                frequency=prescription.frequency,
                duration_days=prescription.duration_days,
                refills=prescription.refills,
                status=prescription.status
            )
            record = result.single()
            return record.data() if record else None
    
    def create_treatment_relationship(
        self,
        medication_id: str,
        disease_id: str,
        effectiveness: str = "high",
        first_line: bool = False
    ) -> Dict[str, Any]:
        """
        Create TREATS relationship between Medication and Disease.
        
        Args:
            medication_id: Medication RxNorm code
            disease_id: Disease ICD-11 code
            effectiveness: Treatment effectiveness
            first_line: Is this first-line treatment?
            
        Returns:
            Relationship data
        """
        query = """
        MATCH (m:Medication {id: $med_id})
        MATCH (d:Disease {id: $disease_id})
        MERGE (m)-[r:TREATS]->(d)
        SET r.effectiveness = $effectiveness,
            r.firstLine = $first_line,
            r.updatedAt = datetime()
        RETURN m.name as medication, d.name as disease, properties(r) as relationship
        """
        
        with self.driver.session() as session:
            result = session.run(
                query,
                med_id=medication_id,
                disease_id=disease_id,
                effectiveness=effectiveness,
                first_line=first_line
            )
            record = result.single()
            return record.data() if record else None
    
    def create_drug_interaction(
        self,
        medication1_id: str,
        medication2_id: str,
        severity: str,
        description: str
    ) -> Dict[str, Any]:
        """
        Create INTERACTS_WITH relationship (bidirectional concept).
        
        Note: We create two directed relationships for bidirectional
        interactions, or query both directions.
        """
        query = """
        MATCH (m1:Medication {id: $med1_id})
        MATCH (m2:Medication {id: $med2_id})
        MERGE (m1)-[r:INTERACTS_WITH {
            severity: $severity,
            description: $description,
            updatedAt: datetime()
        }]->(m2)
        RETURN m1.name as med1, m2.name as med2, properties(r) as interaction
        """
        
        with self.driver.session() as session:
            result = session.run(
                query,
                med1_id=medication1_id,
                med2_id=medication2_id,
                severity=severity,
                description=description
            )
            record = result.single()
            return record.data() if record else None
    
    # =========================================================
    # QUERY RELATIONSHIPS
    # =========================================================
    
    def get_patient_diagnoses(
        self,
        patient_id: str,
        status: str = None
    ) -> List[Dict[str, Any]]:
        """Get all diagnoses for a patient."""
        status_filter = "AND r.status = $status" if status else ""
        
        query = f"""
        MATCH (p:Patient {{id: $patient_id}})-[r:DIAGNOSED_WITH]->(d:Disease)
        WHERE true {status_filter}
        RETURN d.id as disease_code,
               d.name as disease_name,
               r.diagnosedDate as diagnosed_date,
               r.diagnosedBy as diagnosed_by,
               r.status as status,
               r.severity as severity
        ORDER BY r.diagnosedDate DESC
        """
        
        with self.driver.session() as session:
            result = session.run(query, patient_id=patient_id, status=status)
            return [record.data() for record in result]
    
    def get_patient_medications(
        self,
        patient_id: str,
        active_only: bool = True
    ) -> List[Dict[str, Any]]:
        """Get all medications for a patient."""
        status_filter = "AND r.status = 'active'" if active_only else ""
        
        query = f"""
        MATCH (p:Patient {{id: $patient_id}})-[r:TAKES]->(m:Medication)
        WHERE true {status_filter}
        RETURN m.id as medication_code,
               m.name as medication_name,
               m.drugClass as drug_class,
               r.dosage as dosage,
               r.frequency as frequency,
               r.prescribedBy as prescribed_by,
               r.status as status
        ORDER BY r.prescribedDate DESC
        """
        
        with self.driver.session() as session:
            result = session.run(query, patient_id=patient_id)
            return [record.data() for record in result]
    
    def get_disease_treatments(
        self,
        disease_id: str
    ) -> List[Dict[str, Any]]:
        """Get all treatments for a disease."""
        query = """
        MATCH (m:Medication)-[r:TREATS]->(d:Disease {id: $disease_id})
        RETURN m.id as medication_code,
               m.name as medication_name,
               m.drugClass as drug_class,
               r.effectiveness as effectiveness,
               r.firstLine as first_line
        ORDER BY r.firstLine DESC, r.effectiveness DESC
        """
        
        with self.driver.session() as session:
            result = session.run(query, disease_id=disease_id)
            return [record.data() for record in result]
    
    def check_drug_interactions(
        self,
        patient_id: str
    ) -> List[Dict[str, Any]]:
        """
        Check for drug interactions in patient's current medications.
        
        This is a critical safety check in healthcare systems.
        """
        query = """
        MATCH (p:Patient {id: $patient_id})-[t1:TAKES]->(m1:Medication)
        MATCH (p)-[t2:TAKES]->(m2:Medication)
        WHERE t1.status = 'active' AND t2.status = 'active'
          AND m1.id < m2.id  // Avoid duplicates
        OPTIONAL MATCH (m1)-[i:INTERACTS_WITH]-(m2)
        WHERE i IS NOT NULL
        RETURN m1.name as medication1,
               m2.name as medication2,
               i.severity as severity,
               i.description as description
        """
        
        with self.driver.session() as session:
            result = session.run(query, patient_id=patient_id)
            return [record.data() for record in result]
    
    def get_treatment_path(
        self,
        patient_id: str,
        disease_id: str
    ) -> List[Dict[str, Any]]:
        """
        Get the treatment path: Patient -> Disease -> Medications.
        
        Shows how a patient is being treated for a specific condition.
        """
        query = """
        MATCH (p:Patient {id: $patient_id})-[diag:DIAGNOSED_WITH]->(d:Disease {id: $disease_id})
        OPTIONAL MATCH (p)-[takes:TAKES]->(m:Medication)-[treats:TREATS]->(d)
        WHERE takes.status = 'active'
        RETURN p.name as patient,
               d.name as disease,
               diag.status as diagnosis_status,
               collect({
                   medication: m.name,
                   dosage: takes.dosage,
                   frequency: takes.frequency,
                   effectiveness: treats.effectiveness
               }) as treatments
        """
        
        with self.driver.session() as session:
            result = session.run(
                query,
                patient_id=patient_id,
                disease_id=disease_id
            )
            record = result.single()
            return record.data() if record else None
    
    # =========================================================
    # UPDATE RELATIONSHIPS
    # =========================================================
    
    def update_diagnosis_status(
        self,
        patient_id: str,
        disease_id: str,
        new_status: str,
        notes: str = None
    ) -> bool:
        """Update diagnosis status (e.g., active -> resolved)."""
        query = """
        MATCH (p:Patient {id: $patient_id})-[r:DIAGNOSED_WITH]->(d:Disease {id: $disease_id})
        SET r.status = $status,
            r.notes = CASE WHEN $notes IS NOT NULL THEN $notes ELSE r.notes END,
            r.updatedAt = datetime()
        RETURN r
        """
        
        with self.driver.session() as session:
            result = session.run(
                query,
                patient_id=patient_id,
                disease_id=disease_id,
                status=new_status,
                notes=notes
            )
            return result.single() is not None
    
    def refill_prescription(
        self,
        patient_id: str,
        medication_id: str
    ) -> Optional[int]:
        """Process prescription refill, decrementing refills count."""
        query = """
        MATCH (p:Patient {id: $patient_id})-[r:TAKES]->(m:Medication {id: $med_id})
        WHERE r.status = 'active' AND r.refills > 0
        SET r.refills = r.refills - 1,
            r.lastRefillDate = date(),
            r.updatedAt = datetime()
        RETURN r.refills as remaining_refills
        """
        
        with self.driver.session() as session:
            result = session.run(
                query,
                patient_id=patient_id,
                med_id=medication_id
            )
            record = result.single()
            return record["remaining_refills"] if record else None
    
    # =========================================================
    # DELETE RELATIONSHIPS
    # =========================================================
    
    def discontinue_medication(
        self,
        patient_id: str,
        medication_id: str,
        reason: str
    ) -> bool:
        """
        Discontinue a medication (soft delete with status change).
        
        We keep the relationship for medical history but mark as discontinued.
        """
        query = """
        MATCH (p:Patient {id: $patient_id})-[r:TAKES]->(m:Medication {id: $med_id})
        WHERE r.status = 'active'
        SET r.status = 'discontinued',
            r.discontinuedDate = date(),
            r.discontinuedReason = $reason,
            r.updatedAt = datetime()
        RETURN r
        """
        
        with self.driver.session() as session:
            result = session.run(
                query,
                patient_id=patient_id,
                med_id=medication_id,
                reason=reason
            )
            return result.single() is not None
    
    def delete_relationship(
        self,
        from_label: str,
        from_id: str,
        rel_type: str,
        to_label: str,
        to_id: str
    ) -> bool:
        """Delete a specific relationship (hard delete)."""
        query = f"""
        MATCH (a:{from_label} {{id: $from_id}})-[r:{rel_type}]->(b:{to_label} {{id: $to_id}})
        DELETE r
        RETURN count(*) as deleted
        """
        
        with self.driver.session() as session:
            result = session.run(query, from_id=from_id, to_id=to_id)
            return result.single()["deleted"] > 0


# ============================================================
# DEMONSTRATION
# ============================================================

if __name__ == "__main__":
    print("=" * 60)
    print("Relationships - Demonstration")
    print("=" * 60)
    
    try:
        manager = RelationshipManager()
        
        # Create diagnosis
        print("\n📌 Creating Diagnosis Relationship")
        print("-" * 40)
        
        diagnosis = Diagnosis(
            diagnosed_date=date(2024, 1, 15),
            diagnosed_by="Dr. Sarah Johnson",
            status="active",
            severity="moderate",
            notes="Initial diagnosis based on HbA1c > 6.5%"
        )
        
        result = manager.create_diagnosis("P001", "E11", diagnosis)
        if result:
            print(f"   {result['patient']} diagnosed with {result['disease']}")
        
        # Create prescription
        print("\n📌 Creating Prescription Relationship")
        print("-" * 40)
        
        prescription = Prescription(
            prescribed_date=date(2024, 1, 15),
            prescribed_by="Dr. Sarah Johnson",
            dosage="500mg",
            frequency="twice daily",
            duration_days=90,
            refills=3
        )
        
        result = manager.create_prescription("P001", "6809", prescription)
        if result:
            print(f"   {result['patient']} prescribed {result['medication']}")
        
        # Create treatment relationship
        print("\n📌 Creating Treatment Relationship")
        print("-" * 40)
        
        result = manager.create_treatment_relationship(
            "6809", "E11",
            effectiveness="high",
            first_line=True
        )
        if result:
            print(f"   {result['medication']} TREATS {result['disease']}")
        
        # Query patient diagnoses
        print("\n📌 Querying Patient Diagnoses")
        print("-" * 40)
        
        diagnoses = manager.get_patient_diagnoses("P001")
        for d in diagnoses:
            print(f"   {d['disease_name']} - {d['status']} ({d['severity']})")
        
        # Query treatment path
        print("\n📌 Treatment Path Query")
        print("-" * 40)
        
        path = manager.get_treatment_path("P001", "E11")
        if path:
            print(f"   Patient: {path['patient']}")
            print(f"   Condition: {path['disease']}")
            print(f"   Treatments: {path['treatments']}")
        
        print("\n" + "=" * 60)
        print("✅ Relationships demonstration complete!")
        print("=" * 60)
        
    except Exception as e:
        print(f"Error: {e}")
